﻿using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using Av.Utils;
using System.Text;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;

namespace JrSnifTest
{

    /// <summary>
    /// Common delegate for notifications
    /// </summary>
    public delegate void MyNotificationHandler(object sender, EventArgs e);

    public partial class Form1 : Form
    {

        /// <summary>
        /// Indicates that packet filtering rules were changed
        /// </summary>
        public event MyNotificationHandler PacketFilterChanged = null;

        protected Listener m_listener = null;

        // IPs to be used in filter
        protected Dictionary<int, string> m_arIpsToWatch = new Dictionary<int, string>();

        /// <summary>
        /// Packet filtering rules
        /// </summary>
        protected IPacketMatcher m_packetFilter = new PacketMatcher();
        
        /// <summary>
        /// Systray icon
        /// </summary>
        protected NotifyIcon m_icon = null;

        #region " Get/set text methods (invoked) "

        delegate void SetTextDelegate(string name, string value);
        delegate string GetTextDelegate(string name);

        /// <summary>
        /// Returns text of control.
        /// </summary>
        /// <param name="name">Name of control</param>
        /// <returns>Text of control. Returns "checked"/"" or "" for CheckBox</returns>
        private string GetText(string name)
        {
            string s = "";

            try
            {
                if(this.InvokeRequired)
                {
                    this.Invoke(new GetTextDelegate(GetText), name);
                } else
                {
                    Control[] ar = this.Controls.Find(name, false);
                    if((ar != null) && (ar.Length > 0))
                    {
                        if(ar[0].GetType() == typeof(CheckBox))
                        {
                            s = ((CheckBox)ar[0]).Checked ? "checked" : "";
                        } else
                        {
                            s = ar[0].Text;
                        }
                    }
                }
            } catch(Exception ex)
            {
            }

            return s;
        }

        /// <summary>
        /// Thread safe AddText
        /// </summary>
        /// <param name="name">Name of control to add text</param>
        /// <param name="value">Text to add</param>
        private void AddText(string name, string value)
        {
            try
            {
                if(this.InvokeRequired)
                {
                    this.Invoke(new SetTextDelegate(AddText), name, value);
                } else
                {
                    Control[] ar = this.Controls.Find(name, false);
                    if((ar != null) && (ar.Length > 0))
                    {
                        ar[0].Text += value;
                        if(ar[0].GetType() == typeof(TextBox))
                        {
                            ((TextBox)ar[0]).SelectionStart = Int32.MaxValue;
                        }
                    }
                }
            } catch(Exception)
            {
            }
        }

        /// <summary>
        /// Thread safe SetText
        /// </summary>
        /// <param name="name">Name of control to set text</param>
        /// <param name="value">Text to set</param>
        private void SetText(string name, string value)
        {
            try
            {
                if(this.InvokeRequired)
                {
                    this.Invoke(new SetTextDelegate(SetText), name, value);
                } else
                {
                    Control[] ar = this.Controls.Find(name, false);
                    if((ar != null) && (ar.Length > 0))
                    {
                        ar[0].Text = value;
                    }
                }

            } catch(Exception ex)
            {
            }
        }

        #endregion

        public Form1()
        {
            InitializeComponent();
        }

        private void OnFormClosed(object sender, FormClosedEventArgs e)
        {
            if(m_icon != null)
            {
                m_icon.Dispose();
            }
        }

        private void OnFormLoad(object sender, EventArgs e)
        {
            try
            {
                Log4cs.Log("Starting {0} (v{1})...", Settings.Name, Settings.Version);
                Debug("Starting {0}...", Settings.NameVersion);

                // In case we need only one instance of program
                EnsureSingleApplication();

                this.Text = Settings.NameVersion;

                // Creates icons using emebedded icon
                CreateFormIcons();

                // Create context menu for tray icon
                MenuItem[] arMenu = this.CreateMenuItems();
                if(arMenu != null)
                {
                    m_icon.ContextMenu = new ContextMenu(arMenu);
                }

                // Hides application (form) if necessary
                //HideApplication();

                CreateFilterRulesList();

                CreateLocalIpsList();

                Log4cs.Log("Selected IP to watch: {0}", lstLocalIps.SelectedItem);
                m_listener = new Listener(lstLocalIps.SelectedItem.ToString());
                m_listener.NewDataCaptured += new MyNotificationHandler(OnNewDataCaptured);
                PacketFilterChanged += new MyNotificationHandler(OnPacketFilterChanged);

            } catch(ApplicationException ex)
            {
                Debug(Importance.Error, "Application already run, check logs!");
                MessageBox.Show(ex.ToString(), string.Format("{0} (v{1})", Settings.Name, Settings.Version), MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.Close();
            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error loading main form!");
                Log4cs.Log(Importance.Debug, ex.ToString());
                MessageBox.Show("Error loading application!", string.Format("{0} (v{1})", Settings.Name, Settings.Version), MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.Close();
            }
        }

        private void CreateLocalIpsList()
        {
            lstLocalIps.Items.Clear();
            NetworkInterface[] nis = NetworkInterface.GetAllNetworkInterfaces();
            int indexOfNotLoopback = 0;
            for(int i = 0; (nis != null) && (i < nis.Length); i++)
            {
                foreach(UnicastIPAddressInformation uniCast in nis[i].GetIPProperties().UnicastAddresses)
                {
                    if(uniCast.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetworkV6)
                    {
                        // Choose selected if not loopback
                        if( !IPAddress.IsLoopback(uniCast.Address) )
                        {
                            indexOfNotLoopback = lstLocalIps.Items.Count;
                        }
                        lstLocalIps.Items.Add(uniCast.Address);
                    }
                }
                lstLocalIps.SelectedIndex = indexOfNotLoopback;
            }
        }

        void OnPacketFilterChanged(object sender, EventArgs e)
        {
            Log4cs.Log("Packet filtering rules changed:{0}{1}", Environment.NewLine, m_packetFilter.ToString());
            if( m_listener.IsListening )
            {
                MessageBox.Show("Packet filtering rules were changed. Stop and start sniffer", Settings.NameVersion, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        void OnNewDataCaptured(object sender, EventArgs e)
        {
            Debug(((ListenerEventArgs)e).Msg);
        }

        /// <summary>
        /// Hides application (form) and from taskbar
        /// </summary>
        protected void HideApplication()
        {
            this.ShowInTaskbar = false;
            this.Visible = false;
        }

        /// <summary>
        /// Hides from Alt-TAB
        /// </summary>
        //protected override CreateParams CreateParams
        //{
        //    get
        //    {
        //        CreateParams cp = base.CreateParams;

        //        // Turns on WS_EX_TOOLWINDOW style bit to hide from Alt-TAB list
        //        cp.ExStyle |= 0x80;

        //        return cp;
        //    }
        //}

        /// <summary>
        /// Creates tray icon from embedded to project icon. Throws execptions
        /// </summary>
        private void CreateFormIcons()
        {
            // Create tray icon
            Assembly asm = Assembly.GetExecutingAssembly();
            FileInfo fi = new FileInfo(asm.GetName().Name);
            using(Stream s = asm.GetManifestResourceStream(string.Format("{0}.av1.ico", fi.Name)))
            {
                // Create icon to be used in Form and Tray
                Icon icon = new Icon(s);
                
                Icon = new Icon(icon, icon.Size);

                m_icon = new NotifyIcon();
                m_icon.Visible = true;
                m_icon.Icon = new Icon(icon, icon.Size);

                icon.Dispose();
            }
        }

        /// <summary>
        /// Ensures that application is the only. Throws ApplicationException if there is such application
        /// </summary>
        private void EnsureSingleApplication()
        {
            bool createdNew = false;
            Mutex mx = new Mutex(false, Settings.Name, out createdNew);
            Log4cs.Log(Importance.Debug, "Is mutex created: {0}", createdNew);

            // If application is already running
            if(createdNew == false)
            {
                throw new ApplicationException(Settings.Name + " application is already running!");
            }
        }


        #region " Debug to UI console "

        /// <summary>
        /// Outputs message to UI output console
        /// </summary>
        /// <param name="msg">Message to input, could be used like string.Format()</param>
        /// <param name="args"></param>
        protected void Debug(string msg, params object[] args)
        {
            Debug(Importance.Info, msg, args);
        }

        /// <summary>
        /// Outputs message to UI output console
        /// </summary>
        /// <param name="level">Level of imortance - Error, Info, etc...</param>
        /// <param name="msg">Message to input, could be used like string.Format()</param>
        /// <param name="args"></param>
        protected void Debug(Importance level, string msg, params object[] args)
        {
            StringBuilder sb = new StringBuilder();
            if(level != Importance.No)
            {
                sb.AppendFormat("[{0}] ", level.ToString().ToUpper());
            }

            sb.AppendFormat(msg, args);
            sb.AppendLine();
            this.AddText("txtOutput", sb.ToString());
            txtOutput.SelectionStart = int.MaxValue;
            txtOutput.ScrollToCaret();
        }
        
        #endregion


        #region " Context menu methods "

        /// <summary>
        /// Creates context menu for tray icon
        /// </summary>
        /// <returns></returns>
        private MenuItem[] CreateMenuItems()
        {
            MenuItem[] arMenu = null;

            try
            {
                // Got quantity of menus
                arMenu = new MenuItem[MyMenu.Size];

                for(int i = 0; i < MyMenu.Size; i++)
                {
                    arMenu[i] = new MenuItem(MyMenu.ToName(i), OnContextMenuClicked);
                }

                // By default status thread is stopped, so disable "Stop" command
                arMenu[MyMenu.Position.Stop].Enabled = false;

                // Format "Version"
                arMenu[MyMenu.Position.Version].Text = string.Format("{0} (v{1})", Settings.Name, Settings.Version);

            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error creating menu items!");
                Log4cs.Log(Importance.Debug, ex.ToString());
            }

            return arMenu;
        }

        /// <summary>
        /// Handles clicks on systray icon menu
        /// </summary>
        void OnContextMenuClicked(object sender, EventArgs e)
        {
            try
            {
                switch(((MenuItem)sender).Index)
                {
                    case MyMenu.Position.Start:
                        OnStartStopClicked(null, null);
                        break;
                    case MyMenu.Position.Stop:
                        OnStartStopClicked(null, null);
                        break;
                    case MyMenu.Position.Version:
                        MessageBox.Show("Simple Aiva Helper by mr. Aleksej Vasinov", Settings.NameVersion, MessageBoxButtons.OK, MessageBoxIcon.Information);
                        break;
                    case MyMenu.Position.Reload:
                        ReloadSettings();
                        break;
                    default:
                        this.Close();
                        break;
                }

            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error handling context menu click!");
                Log4cs.Log(Importance.Debug, ex.ToString());
            }
        }


        /// <summary>
        /// Reloads setting. Stops thread if necessary
        /// </summary>
        private void ReloadSettings()
        {
            Log4cs.Log("Reloading settings...");
            Settings.Load();
        }

        /// <summary>
        /// Starts/stops application if needed :)
        /// </summary>
        private void OnStartStopClicked(object sender, object e)
        {
            Log4cs.Log("Start/stop is clicked...");
            OnStartStopSniffingClicked(sender, null);
        }

        #endregion

        private void OnStartStopSniffingClicked(object sender, EventArgs e)
        {
            try
            {
                if(m_listener.IsListening)
                {
                    m_listener.Stop();
                } else
                {
                    m_listener.PacketFilter = m_packetFilter;
                    // TCP protocol
                    //m_listener.PacketFilter.AddIpProtocol(0x06);
                    //// Destination IP
                    //m_listener.PacketFilter.AddIp("109.111.3.69");
                    //m_listener.PacketFilter.AddPort(64396);

                    m_listener.Start();
                }

            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error starting/stopping sniffer!");
                Log4cs.Log(Importance.Debug, ex.ToString());
                Debug(Importance.Error, "Error starting/stopping sniffer!");
            }

            if(m_listener.IsListening)
            {
                m_icon.ContextMenu.MenuItems[MyMenu.Position.Start].Enabled = false;
                m_icon.ContextMenu.MenuItems[MyMenu.Position.Stop].Enabled = true;
                this.SetText("btnStartStopSniffing", "Stop");
            } else
            {
                m_icon.ContextMenu.MenuItems[MyMenu.Position.Start].Enabled = true;
                m_icon.ContextMenu.MenuItems[MyMenu.Position.Stop].Enabled = false;
                this.SetText("btnStartStopSniffing", "Start");
            }
        }

        private void OnIpTextChanged(object sender, EventArgs e)
        {
            try
            {
                TextBox tb = (TextBox)sender;
                //Debug("IP #{0}...", Common.GetLastTokenAfterUnderscore(tb.Name) );
                int nrOfTextBox = int.Parse(Common.GetLastTokenAfterUnderscore(tb.Name));

                // Check if IP was added
                if( !m_arIpsToWatch.ContainsKey(nrOfTextBox) )
                {
                    OnIpToWatchAdded(nrOfTextBox, tb.Text.Trim());
                }else if( m_arIpsToWatch[nrOfTextBox] != tb.Text.Trim() )
                {
                    OnIpToWatchChanged(nrOfTextBox, tb.Text.Trim());
                }

            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error watching IP changes!");
                Log4cs.Log(Importance.Debug, ex.ToString());
                Debug(Importance.Error, "Error on IP change!");
            }
        }

        private void OnIpToWatchAdded(int nrOfTextBox, string ip)
        {
            m_arIpsToWatch[nrOfTextBox] = ip;
            PacketMatcher pm = (PacketMatcher)CreatePacketFilterFromRules();

            // Change rules if some rules were applied
            if(!pm.IsEmpty)
            {
                m_packetFilter = pm;
                if(PacketFilterChanged != null)
                {
                    PacketFilterChanged("Form1", null);
                }
            }
        }

        private void OnIpToWatchChanged(int nrOfTextBox, string ip)
        {
            m_arIpsToWatch[nrOfTextBox] = ip;
            PacketMatcher pm = (PacketMatcher)CreatePacketFilterFromRules();

            // Change rules if some rules were applied
            if(!pm.IsEmpty)
            {
                m_packetFilter = pm;
                if(PacketFilterChanged != null)
                {
                    PacketFilterChanged("Form1", null);
                }
            }
        }

        protected IPacketMatcher CreatePacketFilterFromRules()
        {
            PacketMatcher pm = new PacketMatcher();
            IPAddress ip = new IPAddress(0);

            if( checkTcp.Checked )
            {
                pm.AddIpProtocol(0x06);
            }
            if(checkUdp.Checked)
            {
                pm.AddIpProtocol(0x11);
            }
            if(checkIcmp.Checked)
            {
                pm.AddIpProtocol(0x01);
            }

            foreach(string s in m_arIpsToWatch.Values)
            {
                // Check if there are several IP 127.0.0.1, 127.0.0.5
                string[] ar = s.Trim().Split(", ".ToCharArray());
                for(int i = 0; i < ar.Length; i++)
                {
                    if(IPAddress.TryParse(ar[i].Trim(), out ip))
                    {
                        pm.AddIp(ar[i]);
                    }
                }
            }

            foreach(int port in m_arPortsToWatch.Values)
            {
                if( port > 0 )
                {
                    pm.AddPort(port);
                }
            }

            return pm;
        }

        protected Dictionary<int, int> m_arPortsToWatch = new Dictionary<int, int>();
        private void OnPortTextChanged(object sender, EventArgs e)
        {
            try
            {
                TextBox tb = (TextBox)sender;
                int nrOfTextBox = int.Parse(Common.GetLastTokenAfterUnderscore(tb.Name));
                int port = 0;
                bool isChanged = false;

                // If user enters port number
                if(tb.Text.Trim() != "")
                {

                    port = int.Parse(tb.Text.Trim());

                    // If new port or old was changed
                    if(!m_arPortsToWatch.ContainsKey(nrOfTextBox) || (m_arPortsToWatch[nrOfTextBox] != port))
                    {
                        Log4cs.Log("Port #{0}: {1}", nrOfTextBox, port);
                        m_arPortsToWatch[nrOfTextBox] = port;
                        isChanged = true;
                    }
                    
                } else if( m_arPortsToWatch.ContainsKey(nrOfTextBox) )
                {
                    // Port was deleted
                    if( m_arPortsToWatch[nrOfTextBox] != 0 )
                    {
                        m_arPortsToWatch[nrOfTextBox] = 0;
                        isChanged = true;
                    }
                }

                // Check
                if( isChanged )
                {
                    PacketMatcher pm = (PacketMatcher)CreatePacketFilterFromRules();

                    // Change rules if some rules were applied
                    if(!pm.IsEmpty)
                    {
                        Log4cs.Log("Port was changed, need to fire event!");
                        m_packetFilter = pm;
                        if(PacketFilterChanged != null)
                        {
                            PacketFilterChanged("Form1", null);
                        }
                    }

                    Debug("New packet filtering rules:{0}{1}", Environment.NewLine, pm.ToString());
                }

            }catch(FormatException)
            {
                Debug(Importance.Error, "Bad port number!");
            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error watching port change!");
                Log4cs.Log(Importance.Debug, ex.ToString());
                Debug(Importance.Error, "Error on port change!");
            }
        }

        private void OnFilterRulesIndexChanged(object sender, EventArgs e)
        {
            ComboBox cb = (ComboBox)sender;
            ClearPortsAndIpsToWatch();
            if(cb.SelectedIndex > 0)
            {
                if( cb.SelectedIndex == 1 )
                {
                    m_arIpsToWatch[1] = "193.178.175.38";
                    txtIp_1.Text = "193.178.175.38";
                    m_arPortsToWatch[1] = 1001;
                    txtPort_1.Text = "1001";
                    checkTcp.Checked = true;

                    PacketMatcher pm = (PacketMatcher)CreatePacketFilterFromRules();
                    if( !pm.IsEmpty )
                    {
                        m_packetFilter = pm;
                        PacketFilterChanged("Form1", null);
                    }
                    //if( PacketFilterChanged != null )
                    //{
                    //    PacketFilterChanged("Form1", null);
                    //}
                }
            }
        }

        protected Dictionary<int, string> m_arFilterRules = new Dictionary<int, string>();
        private void CreateFilterRulesList()
        {
            m_arFilterRules.Add(1, "TradeStation download");
            lstFilterRules.Items.Add(m_arFilterRules[1]);
        }

        private void ClearPortsAndIpsToWatch()
        {
            //int total = 6;
            //for(int i = 0; i < total; i++)
            //{
            //    m_arPortsToWatch[i] = 0;
            //    m_arIpsToWatch[i] = "";
            //}
            txtIp_1.Text = "";
            txtIp_2.Text = "";
            txtIp_3.Text = "";
            txtIp_4.Text = "";
            txtIp_5.Text = "";
            txtIp_6.Text = "";

            txtPort_1.Text = "";
            txtPort_2.Text = "";
            txtPort_3.Text = "";
            txtPort_4.Text = "";
            txtPort_5.Text = "";
            txtPort_6.Text = "";

            checkTcp.Checked = false;
            checkUdp.Checked = false;
            checkIcmp.Checked = false;
        }

        private void OnLocalIpChanged(object sender, EventArgs e)
        {

        }


    }  // END CLASS Form1

}
